package form

import (
	"fmt"
	"gitee.com/gcom/gbox/tag"
	"reflect"
	"regexp"
	"strconv"
	"time"
)

type ConvertHandler func(value reflect.Value, text string, opt tag.TagOptions) error

type Converter interface {
	Convert(value reflect.Value, text string, opt tag.TagOptions) error
}

// Default convert handlers for basic types.
var builtinConverters = map[reflect.Kind]ConvertHandler{
	reflect.Bool:    convertBool,
	reflect.Float32: convertFloat32,
	reflect.Float64: convertFloat64,
	reflect.Int:     convertInt,
	reflect.Int8:    convertInt8,
	reflect.Int16:   convertInt16,
	reflect.Int32:   convertInt32,
	reflect.Int64:   convertInt64,
	reflect.String:  convertString,
	reflect.Uint:    convertUint,
	reflect.Uint8:   convertUint8,
	reflect.Uint16:  convertUint16,
	reflect.Uint32:  convertUint32,
	reflect.Uint64:  convertUint64,
}

var builtinTypeConverters = map[reflect.Type]ConvertHandler{
	reflect.TypeOf(time.Time{}): convertTime,
}

// -- builtin convert handlers
func convertBool(value reflect.Value, text string, opt tag.TagOptions) error {
	v, err := strconv.ParseBool(text)
	if err == nil {
		value.Set(reflect.ValueOf(v))
		return nil
	}
	return err
}

func convertFloat32(value reflect.Value, text string, opt tag.TagOptions) error {
	v, err := strconv.ParseFloat(text, 32)
	if err == nil {
		value.Set(reflect.ValueOf(float32(v)))
		return nil
	}
	return err
}

func convertFloat64(value reflect.Value, text string, opt tag.TagOptions) error {
	v, err := strconv.ParseFloat(text, 64)
	if err == nil {
		value.Set(reflect.ValueOf(v))
		return nil
	}
	return err
}

func convertInt(value reflect.Value, text string, opt tag.TagOptions) error {
	v, err := strconv.ParseInt(text, 10, 0)
	if err == nil {
		value.Set(reflect.ValueOf(int(v)))
		return nil
	}
	return err
}

func convertInt8(value reflect.Value, text string, opt tag.TagOptions) error {
	v, err := strconv.ParseInt(text, 10, 8)
	if err == nil {
		value.Set(reflect.ValueOf(int8(v)))
		return nil
	}
	return err
}

func convertInt16(value reflect.Value, text string, opt tag.TagOptions) error {
	v, err := strconv.ParseInt(text, 10, 16)
	if err == nil {
		value.Set(reflect.ValueOf(int16(v)))
		return nil
	}
	return err
}

func convertInt32(value reflect.Value, text string, opt tag.TagOptions) error {
	v, err := strconv.ParseInt(text, 10, 32)
	if err == nil {
		value.Set(reflect.ValueOf(int32(v)))
		return nil
	}
	return err
}

func convertInt64(value reflect.Value, text string, opt tag.TagOptions) error {
	v, err := strconv.ParseInt(text, 10, 64)
	if err == nil {
		value.Set(reflect.ValueOf(v))
		return nil
	}
	return err
}

func convertString(value reflect.Value, text string, opt tag.TagOptions) error {
	value.Set(reflect.ValueOf(text))
	return nil
}

func convertUint(value reflect.Value, text string, opt tag.TagOptions) error {
	v, err := strconv.ParseUint(text, 10, 0)
	if err == nil {
		value.Set(reflect.ValueOf(uint(v)))
		return nil
	}
	return err
}

func convertUint8(value reflect.Value, text string, opt tag.TagOptions) error {
	v, err := strconv.ParseUint(text, 10, 8)
	if err == nil {
		value.Set(reflect.ValueOf(uint8(v)))
		return nil
	}
	return err
}

func convertUint16(value reflect.Value, text string, opt tag.TagOptions) error {
	v, err := strconv.ParseUint(text, 10, 16)
	if err == nil {
		value.Set(reflect.ValueOf(uint16(v)))
		return nil
	}
	return err
}

func convertUint32(value reflect.Value, text string, opt tag.TagOptions) error {
	v, err := strconv.ParseUint(text, 10, 32)
	if err == nil {
		value.Set(reflect.ValueOf(uint32(v)))
		return nil
	}
	return err
}

func convertUint64(value reflect.Value, text string, opt tag.TagOptions) error {
	v, err := strconv.ParseUint(text, 10, 64)
	if err == nil {
		value.Set(reflect.ValueOf(uint64(v)))
		return nil
	}
	return err
}

var numberPattern = regexp.MustCompile("^\\d+$")

func convertTime(value reflect.Value, text string, opt tag.TagOptions) error {
	var tm time.Time
	var loc = time.Local
	var format = time.RFC3339
	var err error
	if a, _ := opt.Attr("loc"); a != nil {
		if l, ok := a.(string); ok {
			loc, err = time.LoadLocation(l)
			if err != nil {
				return err
			}
		} else if l, ok := a.(int64); ok {
			loc = time.FixedZone("GMT", int(l)*3600)
		} else {
			return fmt.Errorf("invalid time location value %v", l)
		}
	}
	if f, err := opt.StringAttr("format"); err == nil {
		if f != nil {
			format = *f
		}
	} else {
		return fmt.Errorf("invalid time format value %v", f)
	}

	if numberPattern.Match([]byte(text)) {
		ts, err := strconv.ParseInt(text, 10, 64)
		if err != nil {
			return err
		}
		tm = time.Unix(ts/1000, ts%1000*10^9).In(loc)
		if value.Type().Kind() == reflect.Ptr {
			value.Elem().Set(reflect.ValueOf(&tm).Elem())
		} else {
			value.Set(reflect.ValueOf(tm))
		}
		return nil
	}

	tm, err = time.ParseInLocation(format, text, loc)
	if err != nil {
		return err
	}
	if value.Type().Kind() == reflect.Ptr {
		value.Elem().Set(reflect.ValueOf(&tm).Elem())
	} else {
		value.Set(reflect.ValueOf(tm))
	}
	return nil
}
